#include "url.h"

ssize_t remove_dot_segments (unsigned char *dest, const unsigned char *src, off_t *query, off_t *fragment, size_t *total)
{
	ssize_t len;
	int state;
	unsigned char c;
	size_t query_len;
	size_t fragment_len;

	len = 0;
	state = 0;
	c = 0;
	query_len = 0;
	fragment_len = 0;

	while (*src) {
		switch (state) {
			case 0:
				if ((*src == '.') && (*(src + 1) == '.') && (*(src + 2) == '/')) { /* "../" */
					src += 3;
				} else if ((*src == '.') && (*(src + 1) == '/')) { /* "./" */
					src += 2;
				} else if ((*src == '/') && (*(src + 1) == '.') && (*(src + 2) == '/')) { /* "/./" */
					src += 2;
				} else if ((*src == '/') && (*(src + 1) == '.') && (*(src + 2) == 0)) { /* "/.\0" */
					*dest++ = '/';
					*dest = 0;

					return ++len;
				} else if ((*src == '/') && (*(src + 1) == '.') && (*(src + 2) == '.') && (*(src + 3) == '/')) { /* "/../" */
					for (len = len - 1; len >= 0; len--) {
						if (*--dest == '/') {
							break;
						}
					}

					if (len < 0) {
						/* Forbidden. */
						return -2;
					}

					src += 3;
				} else if ((*src == '/') && (*(src + 1) == '.') && (*(src + 2) == '.') && (*(src + 3) == 0)) { /* "/..\0" */
					for (len = len - 1; len >= 0; len--) {
						if (*--dest == '/') {
							break;
						}
					}

					if (len < 0) {
						/* Forbidden. */
						return -2;
					}

					*dest++ = '/';
					*dest = 0;

					return ++len;
				} else if ((*src == '.') && (*(src + 1) == 0)) { /* ".\0" */
					*dest = 0;

					return len;
				} else if ((*src == '.') && (*(src + 1) == '.') && (*(src + 2) == 0)) { /* "..\0" */
					*dest = 0;

					return len;
				} else if (*src == '%') {
					src++;

					state = 2;
				} else {
					*dest++ = *src++;
					len++;

					state = 1;
				}

				break;
			case 1:
				if (*src == '/') {
					state = 0;
				} else if (*src == '%') {
					src++;

					state = 2;
				} else if (*src == '?') {
					src++;
					*dest++ = 0;

					*query = len;
					*fragment = -1;
					query_len = 1;

					state = 4;
				} else if (*src == '#') {
					src++;
					*dest++ = 0;

					*query = -1;
					*fragment = len;
					fragment_len = 1;

					state = 5;
				} else {
					*dest++ = *src++;
					len++;
				}

				break;
			case 2:
				if ((*src >= '0') && (*src <= '9')) {
					c = (*src - '0') * 16;
				} else if ((*src >= 'A') && (*src <= 'F')) {
					c = (10 + *src - 'A') * 16;
				} else if ((*src >= 'a') && (*src <= 'f')) {
					c = (10 + *src - 'a') * 16;
				} else {
					return -1;
				}

				src++;

				state = 3;

				break;
			case 3:
				if ((*src >= '0') && (*src <= '9')) {
					c += (*src - '0');
				} else if ((*src >= 'A') && (*src <= 'F')) {
					c += (10 + *src - 'A');
				} else if ((*src >= 'a') && (*src <= 'f')) {
					c += (10 + *src - 'a');
				} else {
					return -1;
				}

				src++;
				*dest++ = c;
				len++;

				state = 1;

				break;
			case 4:
				*dest++ = *src++;
				query_len++;
				break;
			case 5:
				*dest++ = *src++;
				fragment_len++;
				break;
		}
	}

	switch (state) {
		case 0:
		case 1:
			*dest = 0;

			*query = -1;
			*fragment = -1;

			*total = len;
			break;
		case 2:
		case 3:
			return -1;
		case 4:
			*dest = 0;

			*total = len + query_len;
			break;
		case 5:
			*dest = 0;

			*total = len + fragment_len;
			break;
	}

	return len;
}
